1 module hip.api.data.commons;
2 public import hip.api.data.asset;
3 import hip.util.reflection;
4 
5 
6 ///Use @Asset instead of HipAsset.
7 pragma(LDC_no_typeinfo)
8 struct HipAssetUDA(T, Extra)
9 {
10     string path;
11     static if(!(is(T == void)))
12         T function(string data) conversionFunction;
13     static if(!(is(Extra == void)))
14         Extra extra;
15     int start, end;
16 }
17 
18 /** 
19  * Params:
20  *   path = Path where the asset is located. 
21         It may receive an $ for path formatting with numbers(only valid when array is used.)
22  *   conversionFunc = A function with input the data located at "path", and return any data.
23  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
24  *   end = For Arrays. Inclusive.
25  * Returns: 
26  */
27 HipAssetUDA!(T, void) Asset(T)(string path, T function(string) conversionFunc, int start = 0, int end = 0){return HipAssetUDA!(T, void)(path, conversionFunc, start, end);}
28 
29 /** 
30  * Params:
31  *   path = Path where the asset is located. 
32         It may receive an $ for path formatting with numbers(only valid when array is used.)
33  *   conversionFunc = A function with input the data located at "path", and return any data.
34  *   extra = Extra data for instantiating the asset. Usually associated with the constructor
35  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
36  *   end = For Arrays. Inclusive.
37  * Returns: 
38  */
39 HipAssetUDA!(T, Extra) Asset(T, Extra)(string path, T function(string) conversionFunc, Extra extra, int start = 0, int end = 0){return HipAssetUDA!(T, Extra)(path, conversionFunc, extra, start, end);}
40 
41 /**
42  * Params:
43  *   path = Path where the asset is located.
44         It may receive an $ for path formatting with numbers(only valid when array is used.)
45  * Returns:
46  */
47 HipAssetUDA!(void, void) Asset(string path){return HipAssetUDA!(void, void)(path, 0, 0);}
48 /**
49  * Params:
50  *   path = Path where the asset is located.
51         It may receive an $ for path formatting with numbers(only valid when array is used.)
52  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
53  *   end = For Arrays. Inclusive.
54  * Returns:
55  */
56 HipAssetUDA!(void, void) Asset(string path, int start, int end){return HipAssetUDA!(void, void)(path, start, end);}
57 
58 /**
59  * Params:
60  *   path = Path where the asset is located.
61         It may receive an $ for path formatting with numbers(only valid when array is used.)
62  *   extra = Extra data for instantiating with the asset. Usually used with its constructor
63  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
64  *   end = For Arrays. Inclusive.
65  * Returns:
66  */
67 HipAssetUDA!(void, T) Asset(T)(string path, T extra, int start = 0, int end = 0) if(!isFunction!T) {return HipAssetUDA!(void, T)(path, extra, start, end);}
68 
69 
70 template FilterAsset(Attributes...)
71 {
72     import std.traits:isInstanceOf;
73     import std.meta:AliasSeq;
74     static foreach(attr; Attributes)
75         static if(isInstanceOf!(HipAssetUDA, typeof(attr)))
76         	alias FilterAsset = attr;
77 }
78 
79 template GetAssetUDA(Attributes...)
80 {
81     alias asset = FilterAsset!(Attributes);
82     static if(!is(typeof(asset) == void)) //Means it is a real struct.
83         enum GetAssetUDA = asset;
84     else
85         enum GetAssetUDA = HipAssetUDA!(void, void)();
86 }
87 
88 
89 public string[] splitLines(string input)
90 {
91     string[] ret;
92     size_t lastCut = 0;
93     foreach(i, ch; input)
94     {
95         if(ch == '\n')
96         {
97             ret~= input[lastCut..i];
98             lastCut = i+1;
99         }
100     }
101     if(lastCut < input.length) ret~= input[lastCut..$];
102     return ret;
103 }
104 
105 
106 string[] getModulesFromRoot(string modules, string root)
107 {
108     string[] ret = splitLines(modules);
109 
110     ptrdiff_t rootStart = -1;
111     foreach(i, mod; ret)
112     {
113         if(mod.length < root.length)
114         {
115             if(rootStart == -1)
116                 continue;
117             else
118                 return ret[rootStart..i];
119 
120         }
121         if(mod[0..root.length] == root)
122         {
123             if(rootStart == -1)
124                 rootStart = i;
125         }
126         else if(rootStart != -1)
127             return ret[rootStart..i];
128     }
129     assert(rootStart != -1, "Unable to find root "~root~" in modules list.");
130     return ret[rootStart..$];
131 }
132 
133 IHipAssetLoadTask[] loadAssets()(TypeInfo type, string assetPath, const(ubyte)[] extraData, int start, int end)
134 {
135     import hip.api;
136     int sign = end - start >= 0 ? 1 : -1;
137     ///Include 1 for the upper bounds 
138     int count = ((end - start) * sign) + 1;
139     if(count == 1) return [HipAssetManager.loadAsset(type, assetPath, extraData)];
140     IHipAssetLoadTask[] ret = new IHipAssetLoadTask[count];
141 
142     static string formatStr(string str, int number)
143     {
144         import hip.util.to_string_range;
145         char[32] numSink = 0xff;
146         char[] data = numSink;
147         toStringRange(data, number);
148         int charCount = 0;
149         while(numSink[charCount++] != 0xff){} charCount--;
150         //-1 for the $
151         char[] formattedStr = new char[(cast(int)str.length)-1+charCount];
152         int i = 0;
153         foreach(ch; str)
154         {
155             if(ch == '$')
156                 formattedStr[i..i+=charCount] = numSink[0..charCount];
157             else
158                 formattedStr[i++] = ch;
159         }
160         return cast(string)formattedStr;
161     }
162 
163     foreach(i; 0..count) 
164         ret[i] = HipAssetManager.loadAsset(type, formatStr(assetPath, start+i*sign), extraData);
165     return ret;
166 }
167 
168 mixin template LoadAllAssets(string modules)
169 {
170     import hip.api.data.commons;
171     mixin LoadReferencedAssets!(splitLines(modules));
172 }
173 mixin template LoadReferencedAssets(string[] modules)
174 {
175     //TODO: Improve that loadReferenced to a better
176     void loadReferenced()
177     {
178         import std.stdio;
179         static foreach(modStr; modules)
180         {{
181             mixin("import ",modStr,";");
182             alias theModule = mixin(modStr);
183             static foreach(moduleMemberStr; __traits(allMembers, theModule))
184             {{
185                 alias moduleMember = __traits(getMember, theModule, moduleMemberStr);
186                 static if(is(moduleMember type) && (is(type == class) || is(type == struct)))
187                 {
188                     static foreach(classMemberStr; __traits(derivedMembers, type))
189                     {{
190                         alias classMember = __traits(getMember, type, classMemberStr);
191                         alias assetUDA = GetAssetUDA!(__traits(getAttributes, classMember));
192                         // pragma(msg, assetUDA);
193                         static if(assetUDA.path !is null)
194                         {{
195                             import hip.util.reflection: isArray;
196 
197                             static if(!is(typeof(classMember) == string) && isArray!(typeof(classMember))) alias memberType = typeof(classMember.init[0]);
198                             else alias memberType = typeof(classMember);
199 
200 
201                             const(ubyte)[] extra;
202                             static if(__traits(hasMember, assetUDA, "extra"))
203                             {
204                                 auto v = assetUDA.extra;
205                                 extra = (cast(ubyte*)&v)[0..typeof(v).sizeof];
206 
207                             }
208                             IHipAssetLoadTask[] tasks = loadAssets(typeid(memberType), assetUDA.path, extra, assetUDA.start, assetUDA.end);
209                             memberType* members;
210 
211                             static if(!__traits(compiles, classMember.offsetof)) //Static
212                             {
213                                 static if(!is(memberType == string) && isArray!(typeof(classMember)))
214                                 {
215                                     size_t start = classMember.length;
216                                     classMember.length+= tasks.length;
217                                     members = &classMember[start];
218                                 }
219                                 else
220                                     members = &classMember;
221 
222                                 foreach(i, task; tasks)
223                                 {
224                                     static if(__traits(hasMember, assetUDA, "conversionFunction"))
225                                         task.into(assetUDA.conversionFunction, &members[i]);
226                                     else static if(is(memberType == string))
227                                         task.into(&members[i]);
228                                     else
229                                         task.into!(memberType)(&members[i]);
230                                 }
231                             }
232                         }}
233                     }}
234                 }
235             }}
236         }}
237     }
238 }
239 
240 
241 ///foreachAsset: void foreachAsset(T)(string assetPath)
242 mixin template ForeachAssetInClass(T, alias foreachAsset)
243 {
244     void ForeachAssetInClass()
245     {
246         import std.traits:isFunction;
247         static foreach(member; __traits(derivedMembers, T))
248         {{
249             alias theMember = __traits(getMember, T, member);
250             static if(!isFunction!theMember)
251             {
252                 alias type = typeof(theMember);
253                 enum assetUDA = GetAssetUDA!(__traits(getAttributes, theMember));
254                 static if(assetUDA.path != null)
255                 {
256                     const(ubyte)[] extra;
257                     static if(__traits(hasMember, assetUDA, "extra"))
258                     {
259                         const v = assetUDA.extra;
260                         extra = (cast(const(ubyte*))&v)[0..typeof(v).sizeof];
261                     }
262                     static if(__traits(isTemplate, foreachAsset))
263                         foreachAsset!(type, theMember)(assetUDA.path, extra);
264                     else
265                         foreachAsset(assetUDA.path, extra);
266                 }
267             }
268         }}
269     }
270 }
271 
272 mixin template PreloadAssets()
273 {
274     private void _load(alias theMember)(TypeInfo t, string assetPath, const(ubyte)[] extraData)
275     {
276         import hip.api;
277         HipAssetManager.loadAsset(t, assetPath, extraData).into(&theMember);
278     }
279     alias preload = ForeachAssetInClass!(typeof(this), _load);
280 }
281 
282 /**
283 *   Usage:
284 ```d
285 class SomeScene : IHipPreloadable
286 {
287     mixin Preload; ///IHipPreloadable lets you use Preload symbol. 
288 
289     ///Will load "someTexture.png" inside the member 'texture'
290     @Asset("someTexture.png")
291     IHipTexture texture;
292 
293     ///Loads game levels inside this variable
294     @Asset("gameLevels.txt", &parseGameLevels)
295     GameLevel[] gameLevels
296 
297     ///Doesn't need to call 'preload()' to populate. As it is variable, it will be populated right after its load.
298     @Asset("helpText.txt")
299     static string helpText;
300 
301     void initialize()
302     {
303         preload(); ///Needed to call for populating your assets after the class creation
304     }
305 
306     GameLevel[] parseGameLevels(string data){return [];}
307 }
308 ```
309 */
310 interface IHipPreloadable
311 {
312     void preload();
313     string[] getAssetsForPreload();
314 
315     mixin template Preload()
316     {
317         mixin template finalImpl()
318         {
319             private __gshared string[] _assetsForPreload;
320             private __gshared void getAsset(string asset, const(ubyte)[] extraData){_assetsForPreload~= asset;}
321             private final void loadAsset(T, alias member)(string asset, const(ubyte)[] extraData)
322             {
323                 alias mem = member;
324                 ///Take members that aren't static and populate them after loading.
325                 static if(__traits(compiles, mem.offsetof))
326                 {
327                     ///Try converting the member with conversion function
328                     static if(!__traits(compiles, HipAssetManager.get!T))
329                     {
330                         alias assetUDA = GetAssetUDA!(__traits(getAttributes, mem));
331                         static assert(__traits(hasMember, assetUDA, "conversionFunction"), 
332                         "Type has no conversion function and HipAssetManager can't infer its type.");
333                         mem = assetUDA.conversionFunction(HipAssetManager.get!string(asset));
334                     }
335                     else //Just get from asset manager
336                         mem = HipAssetManager.get!T(asset);
337                 }
338             }
339         }
340         mixin template impl()
341         {
342             string[] getAssetsForPreload()
343             {
344                 if(_assetsForPreload.length == 0)
345                 {
346                     mixin ForeachAssetInClass!(typeof(this), __traits(child, this, getAsset)) f;
347                     f.ForeachAssetInClass;
348                 }
349                 return _assetsForPreload;
350             }
351             void preload()
352             {
353                 mixin ForeachAssetInClass!(typeof(this), loadAsset) f;
354                 f.ForeachAssetInClass;
355             }
356         }
357         
358 
359         ///Deal with override/no override
360         mixin finalImpl;
361         static if(__traits(compiles, typeof(super).preload)){override: mixin impl;}
362         else{mixin impl;}
363     }
364 }
365 
366 /**
367 *   OpenGL Renderer must implement IReloadable for when changing device orientation.
368 */
369 interface IReloadable
370 {
371     bool reload();
372 }
373 
374 
375 
376 enum HipAssetResult : ubyte
377 {
378     waiting,
379     cantLoad,
380     loading,
381     mainThreadLoading,
382     loaded
383 }
384 
385 /** 
386  *  IHipAssetLoadTask is the base return type from any asset you want to `HipAssetManager.load{X}`.
387  *  The loading, unless otherwise stated, is asynchronous. For simple games, most of the time you won't need
388  *  to directly used LoadTask as currently the engine loads all the assets at startup to make it easier
389  *  to prototype a game without needing to think about those tasks.
390  *
391  *  `await` is not supported on WebAssembly export, so, don't use it if you plan to export to web.
392  */
393 interface IHipAssetLoadTask
394 {
395     HipAssetResult result() const;
396     ///Sets the result. Should not exist in user code.
397     HipAssetResult result(HipAssetResult result);
398 
399     HipAsset asset();
400     ///Sets the asset. Should not exist in user code.
401     HipAsset asset(HipAsset asset);
402 
403     bool hasFinishedLoading() const;
404     ///Awaits the asset load process. Can't be used on WebAssembly export
405     void await();
406     ///When the variables finish loading, it will also assign the asset to the variables 
407     void into(void* function(HipAsset asset) castFunc, HipAsset*[] variables...);
408     final void into(T)(T*[] variables...)
409     {
410         into((HipAsset asset) => (cast(void*)cast(T)asset), cast(HipAsset*[])variables);
411     }
412     void into(string*[] variables...);
413 
414     ///May be executed instantly if the asset is already loaded.
415     void addOnCompleteHandler(void delegate(HipAsset) onComplete);
416     void addOnCompleteHandler(void delegate(string) onComplete);
417     ///Executs a step on the loading task. Call `asset` when state is loaded
418     void update();
419     final void into(T)(T function(string) convertFunction, T*[] variables...)
420     {
421         T*[] vars = variables.dup;
422         addOnCompleteHandler((string data)
423         {
424             foreach(v; vars)
425                 *v = convertFunction(data);
426         });
427     }
428 }
429 
430 interface IHipDeserializable
431 {
432     IHipDeserializable deserialize(string data);
433     IHipDeserializable deserialize(void* data);
434 }